-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local utils = require 'mc.utils'
local log = require 'mc.logging'
local cjson = require 'cjson'
local mdb_service = require 'mc.mdb.mdb_service'

local MDB_GET_PATH<const> = 'GetPath'
local DISK_ARRAY_INTERFACE = 'bmc.kepler.Systems.Storage.DiskArray'
local CONTROLLER_INTERFACE = 'bmc.kepler.Systems.Storage.Controller'
local VOLUME_INTERFACE = 'bmc.kepler.Systems.Storage.Volume'
local DRIVE_INTERFACE = 'bmc.kepler.Systems.Storage.Drive'
local bit = require 'bit'

-- GetPath函数返回的无效路径
local INVALID_PATH<const> = ''

local SML_LOGIC_DRIVE_RAID_LEVEL_E = {
    RAID_LEVEL_0 = 0,
    RAID_LEVEL_1 = 1,
    RAID_LEVEL_2 = 2,
    RAID_LEVEL_3 = 3,
    RAID_LEVEL_4 = 4,
    RAID_LEVEL_5 = 5,
    RAID_LEVEL_6 = 6,
    RAID_LEVEL_10 = 10,
    RAID_LEVEL_1ADM = 11,
    RAID_LEVEL_10ADM = 12,
    RAID_LEVEL_1TRIPLE = 13,
    RAID_LEVEL_10TRIPLE = 14,
    RAID_LEVEL_1E = 17,
    RAID_LEVEL_20 = 20,
    RAID_LEVEL_30 = 30,
    RAID_LEVEL_40 = 40,
    RAID_LEVEL_50 = 50,
    RAID_LEVEL_60 = 60,
    RAID_LEVEL_DELETED = 254,
    RAID_LEVEL_UNKNOWN = 255
}

local SML_PD_STATE_E = {
    PD_STATE_UNCONFIGURED_GOOD = 0, -- Unconfigured good drive
    PD_STATE_UNCONFIGURED_BAD = 1, -- Unconfigured bad drive
    PD_STATE_HOT_SPARE = 2, -- Hot spare drive
    PD_STATE_OFFLINE = 3, -- Configured - good drive (data invalid)
    PD_STATE_FAILED = 4, -- Configured - bad drive (data invalid)
    PD_STATE_REBUILD = 5, -- Configured - drive is rebuilding
    PD_STATE_ONLINE = 6, -- Configured - drive is online
    PD_STATE_COPYBACK = 7, -- drive is getting copied
    PD_STATE_SYSTEM = 8, -- drive is exposed and controlled by host
    PD_STATE_SHIELD_UNCONFIGURED = 9, -- UnConfigured - shielded
    PD_STATE_SHIELD_HOT_SPARE = 10, -- Hot Spare - shielded
    PD_STATE_SHIELD_CONFIGURED = 11, -- Configured - shielded
    PD_STATE_FOREIGN = 12, -- With foreign configuration
    PD_STATE_ACTIVE = 13,
    PD_STATE_STANDBY = 14,
    PD_STATE_SLEEP = 15,
    PD_STATE_DST = 16,
    PD_STATE_SMART = 17,
    PD_STATE_SCT = 18,
    PD_STATE_EPD = 19,
    PD_STATE_RAW = 20,
    PD_STATE_READY = 21,
    PD_STATE_NOT_SUPPORTED = 22,
    PD_STATE_PREDICTIVE_FAILURE = 23,
    PD_STATE_DIAGNOSING = 24,
    PD_STATE_INCOMPATIBLE = 25,
    PD_STATE_ERASING = 26,
    PD_STATE_UNKNOWN = 255 -- Unknown state
}

local RF_PHYSICALDISKSTATE_E = {
    RF_PHYSICALDISKSTATE_UNCONFIGUREDGOOD = 'UnconfiguredGood',
    RF_PHYSICALDISKSTATE_UNCONFIGUREDBAD = 'UnconfiguredBad',
    RF_PHYSICALDISKSTATE_HOTSPARE = 'HotSpareDrive',
    RF_PHYSICALDISKSTATE_CONFIGUREDGOOD = 'Offline',
    RF_PHYSICALDISKSTATE_CONFIGUREDBAD = 'Failed',
    RF_PHYSICALDISKSTATE_FOREIGN = 'Foreign',
    RF_PHYSICALDISKSTATE_ACTIVE = 'Active',
    RF_PHYSICALDISKSTATE_STANDBY = 'Standby',
    RF_PHYSICALDISKSTATE_SLEEP = 'Sleep',
    RF_PHYSICALDISKSTATE_DST = 'DSTInProgress',
    RF_PHYSICALDISKSTATE_SMART = 'SMARTOfflineDataCollection',
    RF_PHYSICALDISKSTATE_SCT = 'SCTCommand',
    RF_PHYSICALDISKSTATE_REBUILDING = 'Rebuilding',
    RF_PHYSICALDISKSTATE_ONLINE = 'Online',
    RF_PHYSICALDISKSTATE_GETTINGCOPIED = 'GettingCopied',
    RF_PHYSICALDISKSTATE_CONTROLLEDBYHOST = 'JBOD',
    RF_PHYSICALDISKSTATE_UNCONFIGUREDSHIELDED = 'UnconfiguredShielded',
    RF_PHYSICALDISKSTATE_HOTSPARESHIELDED = 'HotSpareShielded',
    RF_PHYSICALDISKSTATE_CONFIGUREDSHIELDED = 'ConfiguredShielded',
    RF_PHYSICALDISKSTATE_RAW = "Raw",
    RF_PHYSICALDISKSTATE_READY = "Ready",
    RF_PHYSICALDISKSTATE_NOTSUPPORTED = "NotSupported",
    RF_PHYSICALDISKSTATE_PREDICTIVE_FAILURE = "PredictiveFailure",
    RF_PHYSICALDISKSTATE_DIAGNOSING = "Diagnosing",
    RF_PHYSICALDISKSTATE_INCOMPATIBLE = "Incompatible",
    RF_PHYSICALDISKSTATE_ERASING = "EraseInProgress"
}

local RF_DEVIES_MEDIATYPE = {
    RF_DEVIES_MEDIATYPE_HDD_VALUE = 0,
    RF_DEVIES_MEDIATYPE_SSD_VALUE = 1,
    RF_DEVIES_MEDIATYPE_SSM_VALUE = 2
}

local DRIVE_PROTOCOL_MAP = {
    [0] = "Undefined",
    [1] = "parallel SCSI",
    [2] = "SAS",
    [3] = "SATA",
    [4] = "FC",
    [5] = "SATA/SAS",
    [6] = "FC",
    [7] = "PCIe",
    [255] = "unknown"
}

local CONTROLLER_TYPE_ID_E = {
    LSI_3008_WITH_MR = 3,
    LSI_3908_WITH_MR = 14,
    LSI_3916_WITH_MR = 15,
    LSI_3808_WITH_MR = 16,
    PMC_3152_8I_SMART_RAID = 64,
    PMC_2100_8I_SMART_HBA = 65,
    HI1880_SP186_M_16i = 96,
    HI1880_SP186_M_8i = 113,
}

local function use_sub_array(type_id)
    if type_id == CONTROLLER_TYPE_ID_E.PMC_3152_8I_SMART_RAID or
        type_id == CONTROLLER_TYPE_ID_E.PMC_2100_8I_SMART_HBA then
            return true
    end
    if type_id >= CONTROLLER_TYPE_ID_E.HI1880_SP186_M_16i and type_id <= CONTROLLER_TYPE_ID_E.HI1880_SP186_M_8i then
        return true
    end
    return false
end

local m = {}

local function get_real_spans(ref_disk_array_list)
    local span_1880 = {}
    local span_broadcom = {}
    for _, v in pairs(ref_disk_array_list) do
        table.insert((v >= 0x8000) and span_1880 or span_broadcom, v)
    end

    return #span_1880 == 0 and span_broadcom or span_1880
end

function m.is_valid_controller_id(storageid)
    local id = string.match(string.lower(storageid), '^raidstorage(%d+)$')
    if id == nil then
        return false
    end

    local filter = {Id = tonumber(id)}
    local ok, rsp = pcall(mdb_service.get_path, bus, CONTROLLER_INTERFACE, cjson.encode(filter), false)
    if not ok then
        log:error('Verify storageid(%s) failed, err(%s)', storageid, rsp.message)
        return false
    end

    if rsp.Path == INVALID_PATH then
        log:error('Get invalid path')
        return false
    end

    return true
end

function m.is_valid_volume_id(volumeid)
    local id = string.match(string.lower(volumeid), '^logicaldrive(%d+)$')
    if id == nil then
        return false
    end

    local filter = {Id = tonumber(id)}
    local ok, rsp = pcall(mdb_service.get_path, bus, VOLUME_INTERFACE, cjson.encode(filter), false)
    if not ok then
        log:error('Verify volumeid(%s) failed, err(%s)', volumeid, rsp.message)
        return false
    end

    if rsp.Path == INVALID_PATH then
        log:error('Get invalid path')
        return false
    end

    return true
end

function m.if_unknown_to_null(str)
    local unknown = {
        ['N/A'] = true,
        ['Undefined'] = true,
        ['Unknown'] = true,
    }
    if unknown[str] or str == nil or string.len(str) == 0 then
        return cjson.null
    end

    return str
end

function m.get_firmware_version(ver)
    local ver_tmp = ver:match('^%s*(.-)%s*$')
    return m.if_unknown_to_null(ver_tmp)
end

function m.get_supported_device_protocols(device_interface)
    local device_intf = m.if_unknown_to_null(device_interface)
    if device_intf == cjson.null then
        return device_intf
    end

    local s = utils.split(device_intf, ' ')
    return s[1]
end

function m.get_speed_gbps(device_interface)
    local device_intf = m.if_unknown_to_null(device_interface)
    if device_intf == cjson.null then
        return device_intf
    end

    local s = utils.split(device_intf, ' ')
    if s[2] == nil then
        return cjson.null
    end

    return tonumber(string.sub(s[2], 1, -2))
end

local function raid_type_to_raid_level(raid_type)
    if raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1E then
        return 'RAID1E'
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1ADM then
        return 'RAID1(ADM)'
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10ADM then
        return 'RAID10(ADM)'
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1TRIPLE then
        return 'RAID1Triple'
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10TRIPLE then
        return 'RAID10Triple'
    elseif raid_type <= SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_60 then
        return 'RAID' .. raid_type
    end

    return nil
end

function m.get_volumes_raid_level(raid_type)
    local raid_level = raid_type_to_raid_level(raid_type)
    if raid_level == nil then
        return cjson.null
    end
    return raid_level
end

function m.get_volumes_raid_type(raid_level)
    if raid_level == 'RAID1(ADM)' then
        return SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1ADM
    elseif raid_level == 'RAID10(ADM)' then
        return SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10ADM
    elseif raid_level == 'RAID1Triple' then
        return SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1TRIPLE
    elseif raid_level == 'RAID10Triple' then
        return SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10TRIPLE
    else
        local raid_type = raid_level:match('RAID(%d+)')
        return raid_type
    end
end

local function get_disk_array_path(id, ref_controller_id)
    local filter = cjson.encode({Id = id, RefControllerId = ref_controller_id})
    local ok, rsp = pcall(mdb_service.get_path, bus, DISK_ARRAY_INTERFACE, filter, false)
    if not ok then
        log:info("Get disk array path failed, ret: %s", rsp)
        return nil
    end

    return rsp.Path
end

local function get_disk_array_obj(id, ref_controller_id)
    if id == 0xffff then
        return nil
    end

    local path = get_disk_array_path(id, ref_controller_id)
    if path == nil then
        return nil
    end

    local ok, obj = pcall(function ()
        return mdb.get_object(bus, path, DISK_ARRAY_INTERFACE)
    end)
    if not ok then
        log:info('Get disk array object failed, ret = %s', obj)
        return nil
    end

    return obj
end

local function get_drive_by_name(name)
    local filter = {Name = name}
    local ok, rsp = pcall(mdb_service.get_path, bus, DRIVE_INTERFACE, cjson.encode(filter), false)
    if not ok then
        log:info("Get drive path failed, err: %s", rsp.message)
        return nil
    end

    local ok, drive_obj = pcall(function ()
        return mdb.get_object(bus, rsp.Path, DRIVE_INTERFACE)
    end)

    if not ok then
        log:info('Get object failed, ret = %s', drive_obj)
        return nil
    end

    return drive_obj
end

local function get_drive_by_id(drive_id)
    local name = "Disk" .. drive_id
    return get_drive_by_name(name)
end

local function get_extend_span_info(raid_type, array_obj, span, index)
    local raid_level = raid_type_to_raid_level(raid_type)
    if raid_level == nil then
        return
    end
    -- 子属性中ArrayID，UsedSpaceMiB，FreeSpaceMiB, FreeBlocksSpaceMib仅RAID10/50/60/10(ADM)/10Triple支持
    local allow_raid_level<const> = {'RAID10', 'RAID50', 'RAID60', 'RAID10(ADM)', 'RAID10Triple'}
    for _, level in pairs(allow_raid_level) do
        if raid_level == level then
            span['ArrayID'] = array_obj.Id
            span['UsedSpaceMiB'] = array_obj.UsedSpaceMiB
            span['FreeSpaceMiB'] = array_obj.TotalFreeSpaceMiB
            span['FreeBlocksSpaceMiB'] = array_obj.FreeBlocksSpaceMiB
        end
    end
end

local allow_raid_level<const> = {'RAID10', 'RAID50', 'RAID60', 'RAID10(ADM)', 'RAID10Triple'}
function m.get_spans(raid_type, ref_disk_array_list, ref_controller_id)
    local span_list = {}
    local real_spans = get_real_spans(ref_disk_array_list)
    for index, disk_array_id in pairs(real_spans) do
        local obj = get_disk_array_obj(disk_array_id, ref_controller_id)
        if obj == nil then
            goto continue
        end
        local span = {}
        span['SpanName'] = 'Span' .. (index - 1)
        get_extend_span_info(raid_type, obj, span, index - 1)
        table.insert(span_list, span)
        ::continue::
    end

    return span_list
end

local function get_controller_obj_by_id(controller_id)
    local filter = {Id = controller_id}
    local ok, rsp = pcall(mdb_service.get_path, bus, CONTROLLER_INTERFACE, cjson.encode(filter), false)
    if not ok then
        log:info('Verify controller id(%s) failed, err(%s)', controller_id, rsp.message)
        return
    end

    if rsp.Path == INVALID_PATH then
        log:info('Get invalid path of controller(%s)', controller_id)
        return
    end

    local controller_obj
    ok, controller_obj = pcall(function ()
        return mdb.get_object(bus, rsp.Path, CONTROLLER_INTERFACE)
    end)
    if not ok then
        log:info('Get controller object failed, ret = %s', controller_obj)
        return
    end
    return controller_obj
end

function m.get_ssd_caching_enable(ssd_caching_enabled, ref_controller_id)
    local controller_obj = get_controller_obj_by_id(ref_controller_id)
    local type_id = controller_obj and controller_obj.TypeId
    if type_id and type_id >= CONTROLLER_TYPE_ID_E.LSI_3008_WITH_MR and
        type_id <= CONTROLLER_TYPE_ID_E.LSI_3808_WITH_MR then
            return cjson.null
    end

    if ssd_caching_enabled == 1 then
        return true
    end
    if ssd_caching_enabled == 0 then
        return false
    end
    return cjson.null
end

local function get_drive_by_span(drive_names)
    local drives_array
    for _, name in pairs(drive_names) do
        local drive = get_drive_by_name(name)
        if drive ~= nil and drive.Presence == 1 then
            if drives_array == nil then
                drives_array = cjson.json_object_new_array()
            end
            local drive_obj = cjson.json_object_new_object()
            drive_obj.Drives_ID = drive.NodeId
            drive_obj.Name = name
            drives_array[#drives_array+1] = drive_obj
        end
    end
    return drives_array
end

function m.get_sum_spans(ref_disk_array_list, ref_controller_id)
    local span_list = cjson.json_object_new_array()

    local real_spans = get_real_spans(ref_disk_array_list)
    for index, disk_array_id in ipairs(real_spans) do
        local obj = get_disk_array_obj(disk_array_id, ref_controller_id)
        if not obj then
            goto continue
        end

        local span = cjson.json_object_new_object()
        span.Name = 'Span' .. (index - 1)
        span.Drives = get_drive_by_span(obj.RefDrives)
        span_list[#span_list+1] = span
        ::continue::
    end

    return span_list
end


function m.is_valid_refdisk_array_id(drive_id)
    local drive_obj = get_drive_by_id(drive_id)
    if drive_obj == nil then
        log:info('Get drive object failed')
        return false
    end
    -- 判断Drive是否组成过逻辑盘（即RefDiskArrayId是否为65535）
    if drive_obj.RefDiskArrayId == 65535 then
        return false
    end
    return true
end

function m.get_refdisk_array_id(drive_id)
    local drive_obj = get_drive_by_id(drive_id)
    if drive_obj == nil then
        log:info('Get refdisk array id failed')
        return 0xffff
    end

    return drive_obj.RefDiskArrayId
end

function m.get_associate_ld(associated_volumes)
    if associated_volumes == nil then
        log:info('associate volumes is nil')
        return 0xffff
    end
    -- associated_volumes只能有一个成员
    return tonumber(associated_volumes[1]:match('LogicalDrive(%d+)$'))
end

local function FirmwareStatusToStr(status)
    local stateResultMap = {}
    stateResultMap[SML_PD_STATE_E.PD_STATE_UNCONFIGURED_GOOD] =
        RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_UNCONFIGUREDGOOD
    stateResultMap[SML_PD_STATE_E.PD_STATE_UNCONFIGURED_BAD] =
        RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_UNCONFIGUREDBAD
    stateResultMap[SML_PD_STATE_E.PD_STATE_HOT_SPARE] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_HOTSPARE
    stateResultMap[SML_PD_STATE_E.PD_STATE_OFFLINE] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_CONFIGUREDGOOD
    stateResultMap[SML_PD_STATE_E.PD_STATE_FAILED] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_CONFIGUREDBAD
    stateResultMap[SML_PD_STATE_E.PD_STATE_REBUILD] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_REBUILDING
    stateResultMap[SML_PD_STATE_E.PD_STATE_ONLINE] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_ONLINE
    stateResultMap[SML_PD_STATE_E.PD_STATE_EPD] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_ONLINE
    stateResultMap[SML_PD_STATE_E.PD_STATE_COPYBACK] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_GETTINGCOPIED
    stateResultMap[SML_PD_STATE_E.PD_STATE_SYSTEM] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_CONTROLLEDBYHOST
    stateResultMap[SML_PD_STATE_E.PD_STATE_SHIELD_UNCONFIGURED] =
        RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_UNCONFIGUREDSHIELDED
    stateResultMap[SML_PD_STATE_E.PD_STATE_SHIELD_HOT_SPARE] =
        RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_HOTSPARESHIELDED
    stateResultMap[SML_PD_STATE_E.PD_STATE_SHIELD_CONFIGURED] =
        RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_CONFIGUREDSHIELDED
    stateResultMap[SML_PD_STATE_E.PD_STATE_FOREIGN] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_FOREIGN
    stateResultMap[SML_PD_STATE_E.PD_STATE_ACTIVE] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_ACTIVE
    stateResultMap[SML_PD_STATE_E.PD_STATE_STANDBY] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_STANDBY
    stateResultMap[SML_PD_STATE_E.PD_STATE_SLEEP] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_SLEEP
    stateResultMap[SML_PD_STATE_E.PD_STATE_DST] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_DST
    stateResultMap[SML_PD_STATE_E.PD_STATE_SMART] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_SMART
    stateResultMap[SML_PD_STATE_E.PD_STATE_SCT] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_SCT
    stateResultMap[SML_PD_STATE_E.PD_STATE_RAW] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_RAW
    stateResultMap[SML_PD_STATE_E.PD_STATE_READY] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_READY
    stateResultMap[SML_PD_STATE_E.PD_STATE_NOT_SUPPORTED] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_NOTSUPPORTED
    stateResultMap[SML_PD_STATE_E.PD_STATE_PREDICTIVE_FAILURE] =
        RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_PREDICTIVE_FAILURE
    stateResultMap[SML_PD_STATE_E.PD_STATE_DIAGNOSING] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_DIAGNOSING
    stateResultMap[SML_PD_STATE_E.PD_STATE_INCOMPATIBLE] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_INCOMPATIBLE
    stateResultMap[SML_PD_STATE_E.PD_STATE_ERASING] = RF_PHYSICALDISKSTATE_E.RF_PHYSICALDISKSTATE_ERASING
    return stateResultMap[status]
end

local function MediaTypeStr(type)
    if type == RF_DEVIES_MEDIATYPE.RF_DEVIES_MEDIATYPE_HDD_VALUE then
        return 'HDD'
    elseif type == RF_DEVIES_MEDIATYPE.RF_DEVIES_MEDIATYPE_SSD_VALUE then
        return 'SSD'
    elseif type == RF_DEVIES_MEDIATYPE.RF_DEVIES_MEDIATYPE_SSM_VALUE then
        return 'SSM'
    else
        log:error('Get mediatype unknown.')
        return nil
    end
end

local function checkIfEPD(FirmwareStatus)
    if FirmwareStatus == SML_PD_STATE_E.PD_STATE_EPD then
        return true
    else
        return false
    end
end

local function get_drive_members(drive_name, volume_list)
    if volume_list == cjson.null or not volume_list then
        log:info('Get volume_list failed')
        return cjson.null
    end
    local drives = nil
    for _, volume in pairs(volume_list) do
        for _, name in pairs(volume.Drives) do
            if name == drive_name then
                drives = volume.Drives
                break
            end
        end
        if drives then
            break
        end
    end

    if drives == nil then
        return cjson.null
    end

    local members = cjson.json_object_new_array()
    for _, disk_name in pairs(drives) do
        members[#members + 1] = "HDDPlane" .. disk_name
    end
    return members
end

local function get_volume_raid_level(disk_array_obj)
    if disk_array_obj == nil then
        log:info('Get volume raid level failed, disk array object is nil')
        return cjson.null
    end

    return raid_type_to_raid_level(tonumber(disk_array_obj.RAIDType))
end

local function get_free_space_per_drive(disk_array_obj)
    if disk_array_obj == nil then
        log:info('Get free space per drive failed, disk array object is nil')
        return cjson.null
    end
    local drive_count = #disk_array_obj.RefDrives
    if drive_count == 0 then
        return cjson.null
    end

    return disk_array_obj.TotalFreeSpaceMiB / drive_count
end

local function get_free_blocks_space(disk_array_obj, span_count)
    if disk_array_obj == nil then
        log:info('Get free blocks space failed, disk array object(%s) is nil')
        return cjson.null
    end

    local arr = cjson.json_object_new_array()
    for _, block in pairs(disk_array_obj.FreeBlocksSpaceMiB) do
        arr[#arr + 1] = block * span_count
    end
    return arr
end

local function get_span_count_by_volume(drive_obj, volumes_list)
    if not volumes_list then
        log:info("Get SpanCount failed, volumelist is nil")
        return 1
    end
    for _, volume in pairs(volumes_list) do
        if not volume or not volume.Drives then
            goto continue
        end
        for _, drive in pairs(volume.Drives) do
            if drive == drive_obj.Name then
                return volume.SpanCount and volume.SpanCount or 1
            end
        end
        ::continue::
    end
    log:info("Get SpanCount failed, no volume matches")
    return 1
end

function m.get_controller_ref_drives(ref_drives, controller_id, type_id, volumes_list)
    local drives
    local span_count = 1
    for _, drive_name in pairs(ref_drives) do
        local drive_obj = get_drive_by_name(drive_name)
        if drive_obj ~= nil and drive_obj.Presence == 1 then
            if drives == nil then
                drives = cjson.json_object_new_array()
            end
            span_count = 1
            if not use_sub_array(type_id) then
                span_count = get_span_count_by_volume(drive_obj, volumes_list)
            end
            local disk_array_obj = get_disk_array_obj(drive_obj.RefDiskArrayId, controller_id)
            local drive = cjson.json_object_new_object()
            drive.CapacityBytes = drive_obj.CapacityMiB * 1024 * 1024
            drive.DriveID = drive_obj.NodeId
            drive.FirmwareStatus = FirmwareStatusToStr(drive_obj.FirmwareStatus)
            drive.ID = drive_obj.Id
            drive.IsEPD = checkIfEPD(drive_obj.FirmwareStatus)
            drive.MediaType = MediaTypeStr(drive_obj.MediaType)
            drive.Members = get_drive_members(drive_name, volumes_list)
            drive.Name = drive_obj.Name
            drive.Protocol = DRIVE_PROTOCOL_MAP[drive_obj.Protocol]

            drive.TotalFreeSpaceMiB = nil
            if disk_array_obj and disk_array_obj.TotalFreeSpaceMiB then
                drive.TotalFreeSpaceMiB = disk_array_obj.TotalFreeSpaceMiB * span_count
            end
            drive.VolumeRaidLevel = get_volume_raid_level(disk_array_obj)
            drive.NumDrivePerSpan = (disk_array_obj or {}).DriveNumPerSpan
            drive.FreeBlocksSpaceMiB = get_free_blocks_space(disk_array_obj, span_count)
            drive.FreeSpaceMiBPerDrive = get_free_space_per_drive(disk_array_obj)
            drives[#drives + 1] = drive
        end
    end

    if drives == nil then
        return nil
    end

    return drives
end

function m.get_vs_phy_drives(drive_list)
    local res
    for _, path in ipairs(drive_list) do
        local drive = mdb.get_object(bus, path, 'bmc.kepler.Systems.Storage.Drive')
        if drive ~= nil and drive.Presence == 1 and drive.RefControllerId == 255 then
            if res == nil then
                res = cjson.json_object_new_array()
            end

            local drive_obj = cjson.json_object_new_object()
            drive_obj.Drives_ID = drive.NodeId
            drive_obj.Name = drive.Name
            res[#res+1] = drive_obj
        end
    end

    if res == nil then
        return nil
    end

    return res
end

function m.get_controller_drives(drive_list, controller_id, crypto_erase_supported)
    local res
    for _, path in ipairs(drive_list) do
        local drive = mdb.get_object(bus, path, 'bmc.kepler.Systems.Storage.Drive')
        if drive ~= nil and drive.Presence == 1 and drive.RefControllerId == controller_id and
            next(drive.RefVolumeList) == nil and drive.HotspareType ~= 2  and drive.HotspareType ~= 3 then
            --2为局部热备状态
            if res == nil then
                res = cjson.json_object_new_array()
            end

            local drive_obj = cjson.json_object_new_object()
            drive_obj.CryptoEraseSupported = crypto_erase_supported
            drive_obj.Drives_ID = drive.NodeId
            drive_obj.Name = drive.Name
            res[#res+1] = drive_obj
        end
    end

    if res == nil then
        return nil
    end

    return res
end

function m.get_hotspares_drives(drives_list)
    local drives_array
    for _, name in pairs(drives_list) do
        local drive = get_drive_by_name(name)
        if drive ~= nil and drive.Presence == 1 then
            if drives_array == nil then
                drives_array = cjson.json_object_new_array()
            end

            local drive_obj = cjson.json_object_new_object()
            drive_obj.Drives_ID = drive.NodeId
            drive_obj.Name = name
            drives_array[#drives_array+1] = drive_obj
        end
    end

    if drives_array == nil then
        return nil
    end

    local res = cjson.json_object_new_object()
    res.Name = 'Hot Spare'
    res.Drives = drives_array
    return res
end

local function first_insert(array, item)
    local item_name = item.Name
    for _, v in pairs(array) do
        if v.Name == item_name then
            return false
        end
    end
    return true
end

function m.get_volumes_drives(drives_list, crypto_erase_supported, raid_type)
    local drives_array
    local raid_level = raid_type_to_raid_level(raid_type)
    local SpanSupported = false
    for _, value in pairs(allow_raid_level) do
        if value == raid_level then
            SpanSupported = true
        end
    end
    for _, name in pairs(drives_list) do
        local drive = get_drive_by_name(name)
        if drive ~= nil and drive.Presence == 1 and
        (SpanSupported and drive.RefDiskArrayId == 65535 or not SpanSupported) then
            if drives_array == nil then
                drives_array = cjson.json_object_new_array()
            end

            local drive_obj = cjson.json_object_new_object()
            drive_obj.Drives_ID = drive.NodeId
            drive_obj.Name = name
            drive_obj.CryptoEraseSupported = crypto_erase_supported
            drives_array[#drives_array+1] = first_insert(drives_array, drive_obj) and drive_obj or nil
        end
    end

    if drives_array == nil then
        return nil
    end

    return drives_array
end

function m.remove_null_properties(input)
    for i in pairs(input) do
        for k, v in pairs(input[i]) do
            if input[i][k] == cjson.null then
                input[i][k] = nil
            end
        end
    end

    return input
end

function m.get_presence_drives(paths)
    local count = 0
    for _, path in ipairs(paths) do
        local obj = mdb.get_object(bus, path, 'bmc.kepler.Systems.Storage.Drive')
        if obj.Presence == 1 then
            count = count + 1
        end
    end

    return count
end

function m.get_consistency_check_param_period(param)
    local period = 0xffff
    if param ~= nil then
        period = param
    end
    return period
end

function m.get_consistency_check_param_rate(param)
    local rate = 0xff
    if param ~= nil then
        if param == 'Low' then
            rate = 1
        elseif param == 'Medium' then
            rate = 2
        elseif param == 'High' then
            rate = 3
        else
            rate = 4
        end
    end
    return rate
end

function m.get_consistency_check_param_auto_repaired(param)
    local auto_repaired = 0xff
    if param ~= nil then
        auto_repaired = param == false and 0 or 1
    end
    return auto_repaired
end

function m.get_consistency_check_param_delay(param)
    local delay = 0xffffffff
    if param ~= nil then
        delay = param
    end
    return delay
end

function m.get_disk_usage_percent(members)
    -- 无BMA场景, members为空，返回0xFF，web界面显示'--'
    local used_capacity = 0
    local total_capacity = 0
    local useage = 0xFF
    for _, v in ipairs(members) do
        if v.UsedGB ~= 0xFFFF and v.CapacityGB ~= 0xFFFF then
            used_capacity = used_capacity + v.UsedGB
            total_capacity = total_capacity + v.CapacityGB
        end
    end
    if total_capacity ~= 0 then
        useage = math.ceil(100 * used_capacity / total_capacity)
    end
    return useage
end

function m.get_policywritable(policy, policylist)
    if not next(policylist) then
        return false
    end
    return policy
end

function m.get_faultdetails(faultcode)
    local healthStatusStr = ''
    local healthStatusArray = {
        'Memory correctable errors', 'Memory uncorrectable errors', 'Memory ECC errors',
        'NVRAM uncorrectable errors', 'Raid communication loss', 'Card power abnormal'
    }
    if faultcode == 0 then
        return cjson.null
    end

    local firstFlag = true
    for i = 2, #healthStatusArray, 1 do
        if bit.band(faultcode, bit.lshift(1, i - 1)) ~= 0 then
            if firstFlag then
                healthStatusStr = healthStatusStr .. healthStatusArray[i]
                firstFlag = false
            else
                healthStatusStr = healthStatusStr .. ',' .. healthStatusArray[i]
            end
        end
    end

    return healthStatusStr
end

function m.get_span_number(input)
    local span_number = input[1]
    local drive_number = #input[2]
    local type_id = input[3]
    local raid_type = input[4]

    if type(span_number) == 'number' then
        return span_number
    end

    if raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_0 or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1 or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_5 or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_6 or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1ADM or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1TRIPLE then
        return 1
    end

    if raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10 then
        -- 如果硬盘数量大于等于4，且能整除2（每个span最少的物理盘数位2），span_number = 硬盘个数 / 2
        if drive_number >= 4 and drive_number % 2 == 0 then
            return drive_number / 2
        else
            return 2
        end
    end

    if raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_50 or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10ADM or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10TRIPLE then
        -- 如果硬盘数量大于等于6，且能整除3（每个span最少的物理盘数位3），span_number = 硬盘个数 / 3
        if drive_number >= 6 and drive_number % 3 == 0 then
            return drive_number / 3
        else
            return 2
        end
    end

    if raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_60 then
        local min_drive_num
        local min_drive_num_per_span
        if type_id == CONTROLLER_TYPE_ID_E.PMC_2100_8I_SMART_HBA or
            type_id == CONTROLLER_TYPE_ID_E.PMC_3152_8I_SMART_RAID or
            type_id == CONTROLLER_TYPE_ID_E.LSI_3908_WITH_MR or
            type_id == CONTROLLER_TYPE_ID_E.LSI_3916_WITH_MR then
            min_drive_num = 8
            min_drive_num_per_span = 4
        else
            min_drive_num = 6
            min_drive_num_per_span = 3
        end

        if drive_number >= min_drive_num and drive_number % min_drive_num_per_span == 0 then
            return drive_number / min_drive_num_per_span
        end

        return 2
    end

    return 1
end

local function split_str_by_underline(str)
    local segments = {}
    for segment in str:gmatch("([^_]+)") do
        table.insert(segments, segment)
    end
    return segments
end

function m.get_drive_position_by_obj_id(drive_obj_id, hddbackplane_list)
    local path = ''
    for _, obj in pairs(hddbackplane_list) do
        local obj_tab = split_str_by_underline(obj)
        local id = obj_tab[#obj_tab]
        if id == drive_obj_id then
            path = obj
            break
        end
    end
    local ok, board_prop = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.Board')
    if not ok then
        log:error("Get HDDPlane property failed")
        return ''
    end
    return board_prop.Position .. board_prop.SilkText
end

return m